@page "/admin/apiResources"
@attribute [Authorize(Policy = Policies.IsAdmin)]
@layout AdminLayout
@inject HttpClient Http
@inject IMatToaster matToaster
@inject IStringLocalizer<Global> L

@using IdentityServer4.Models
@using IdentityModel
@using System.Net

<h1>@L["ApiResources"]</h1>
<p>API Resources Management.</p>

@if (apiResources == null)
{
    <LoadingBackground ShowLogoBox="true">
        <label>@L["Loading"]</label>
    </LoadingBackground>
}
else
{
    <MatTable Class="mat-elevation-z5" Items="@apiResources" Striped="true">
        <MatTableHeader>
            <th><MatButton Icon="add" Label=@L["New API Resource"] OnClick="@(() => OpenUpsertApiResourceDialog())"></MatButton></th>
            <th>@L["Name"]</th>
            <th>Enabled</th>
            <th>Display Name</th>
            <th>Description</th>
            <th>Scopes</th>
            <th>User Claims</th>
        </MatTableHeader>
        <MatTableRow Context="ApiResourceRow">
            <td>
                <div style="width:155px;">
                    <MatIconButton Icon="edit" OnClick="@(() => OpenUpsertApiResourceDialog(ApiResourceRow))"></MatIconButton>
                    <MatIconButton Icon="delete" OnClick="@(() => OpenDeleteApiResourceDialog(ApiResourceRow))"></MatIconButton>
                </div>
            </td>
            <td><div style="min-width:130px;">@ApiResourceRow.Name</div></td>
            <td><MatSlideToggle TValue="bool" Value="@ApiResourceRow.Enabled" ValueChanged="@((item) => UpdateEnabled(ApiResourceRow))"></MatSlideToggle></td>
            <td><div style="min-width:130px;">@ApiResourceRow.DisplayName</div></td>
            <td><div style="min-width:130px;">@ApiResourceRow.Description</div></td>
            <td>
                <MatChipSet>
                    @foreach (var scope in ApiResourceRow.Scopes)
                        {
                        <MatChip Label="@scope"></MatChip>
                        }
                </MatChipSet>
            </td>
            <td>
                <MatChipSet>
                    @foreach (var userClaim in ApiResourceRow.UserClaims)
                        {
                        <MatChip Label="@userClaim"></MatChip>
                        }
                </MatChipSet>
            </td>
        </MatTableRow>
    </MatTable>
}

<MatDialog @bind-IsOpen="@isUpsertApiResourceDialogOpen">
    <MatDialogTitle>
        @labelUpsertDialogTitle
    </MatDialogTitle>
    <MatDialogContent>
        <EditForm Model="@currentApiResource">
            <FluentValidationValidator />
            <ValidationSummary />
            <MatTabGroup>
                <MatTab Label="Main">
                    <fieldset>
                        <div class="form-group">
                            <MatTextField @bind-Value="@currentApiResource.Name" Disabled="@isCurrentApiContextIdReadOnly" Label="Id" Icon="description" IconTrailing="true" FullWidth="true" Required="true"></MatTextField>
                        </div>
                        <div class="form-group">
                            <MatSlideToggle @bind-Value="@currentApiResource.Enabled" Label="Enabled"></MatSlideToggle>
                        </div>
                        <div class="form-group">
                            <MatTextField @bind-Value="@currentApiResource.DisplayName" Label="Display Name" Icon="description" IconTrailing="true" FullWidth="true"></MatTextField>
                        </div>
                        <div class="form-group">
                            <MatTextField @bind-Value="@currentApiResource.Description" Label="Description" Icon="description" IconTrailing="true" FullWidth="true"></MatTextField>
                        </div>
                        <div class="form-group">
                            <div class="form-group">
                                <MatTextField @bind-Value="@currentApiResource.ScopesText" Label="API Scopes" TextArea="true" FullWidth="true" Required="false"></MatTextField>
                            </div>
                        </div>
                    </fieldset>
                    <h3 class="mat-subtitle1">Allowed Access Token Signing Algorithms</h3>
                    <MatTable Items="@tokenSigningAlgorithmsSelections" Class="mat-elevation-z5, mdc-table-allow" ShowPaging="false" PageSize="@int.MaxValue">
                        <MatTableHeader>
                            <th>@L["Name"]</th>
                            <th>Allow</th>
                        </MatTableHeader>
                        <MatTableRow Context="TokenSigningAlgorithmsRow">
                            <td>@TokenSigningAlgorithmsRow.Name</td>
                            <td><MatCheckbox TValue="bool" @bind-Value="@TokenSigningAlgorithmsRow.IsSelected"></MatCheckbox></td>
                        </MatTableRow>
                    </MatTable>
                </MatTab>
                <MatTab Label="Secrets">
                    <MatTable Items="@currentApiResource.ApiSecrets" Class="mat-elevation-z5" ShowPaging="false" PageSize="@int.MaxValue">
                        <MatTableHeader>
                            <th style="min-width:190px;"><MatButton Icon="add" Label="New Secret" OnClick="@(() => OpenCreateApiResourceSecretDialogOpen())"></MatButton></th>
                            <th>Expiration</th>
                            <th>Description</th>
                        </MatTableHeader>
                        <MatTableRow Context="ApiResourceSecretRow">
                            <td style="text-align:center;">
                                <MatIconButton Icon="delete" OnClick="@(() => OpenDeleteApiResourceSecretDialog(ApiResourceSecretRow))"></MatIconButton>
                            </td>
                            <td>@ApiResourceSecretRow.Expiration</td>
                            <td>@ApiResourceSecretRow.GetDisplayValue()</td>
                        </MatTableRow>
                    </MatTable>
                </MatTab>
                <MatTab Label="User Claims">
                    <fieldset>
                        <div class="form-group" style="margin-top:16px;">
                            <div class="form-group">
                                <MatTextField @bind-Value="@currentApiResource.CustomUserClaimsText" Label="Custom User Claims" TextArea="true" FullWidth="true" Required="false"></MatTextField>
                            </div>
                        </div>
                    </fieldset>
                    <MatDivider></MatDivider>
                    <h3 class="mat-subtitle1">Jwt Claims</h3>
                    <MatTable Items="@jwtClaimSelections" Class="mat-elevation-z5, mdc-table-allow" ShowPaging="false" PageSize="@int.MaxValue">
                        <MatTableHeader>
                            <th>@L["Name"]</th>
                            <th>Required</th>
                        </MatTableHeader>
                        <MatTableRow Context="JwtClaimRow">
                            <td>@JwtClaimRow.Name</td>
                            <td><MatCheckbox TValue="bool" @bind-Value="@JwtClaimRow.IsSelected"></MatCheckbox></td>
                        </MatTableRow>
                    </MatTable>
                </MatTab>
            </MatTabGroup>
        </EditForm>
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@CancelChanges">@L["Cancel"]</MatButton>
        <MatButton OnClick="@UpsertApiResource">@labelUpsertDialogOkButton</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@isDeleteApiResourceDialogOpen" Style="z-index:100">
    <MatDialogTitle><MatIcon Icon="warning"></MatIcon> @L["Confirm Delete"]</MatDialogTitle>
    <MatDialogContent>
        @L["Are you sure you want to delete {0}?", currentApiResource.Name]
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { isDeleteApiResourceDialogOpen = false; })">@L["Cancel"]</MatButton>
        <MatButton OnClick="@DeleteApiResourceAsync">@L["Delete"]</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@isDeleteApiResourceSecretDialogOpen" Style="z-index:100">
    <MatDialogTitle><MatIcon Icon="warning"></MatIcon> @L["Confirm Delete"]</MatDialogTitle>
    <MatDialogContent>
        @L["Are you sure you want to delete {0}?", currentSecret.GetDisplayValue()]
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { isDeleteApiResourceSecretDialogOpen = false; })">@L["Cancel"]</MatButton>
        <MatButton OnClick="@DeleteApiResourceSecret">@L["Delete"]</MatButton>
    </MatDialogActions>
</MatDialog>

<MatDialog @bind-IsOpen="@isCreateApiResourceSecretDialogOpen">
    <MatDialogTitle>New Secret for API Resource @currentApiResource.Name</MatDialogTitle>
    <MatDialogContent>
        <EditForm Model="@currentSecret">
            <FluentValidationValidator />
            <ValidationSummary />
            <fieldset>
                <div class="form-group">
                    <MatTextField @bind-Value="@currentSecret.Description" Label="Description" Icon="description" IconTrailing="true" FullWidth="true"></MatTextField>
                </div>
                <div class="form-group">
                    <MatTextField @bind-Value="@currentSecret.Value" Label="Secret" Icon="lock_outline" IconTrailing="true" Required="true"></MatTextField>
                    <MatIconButton OnClick="@GenerateSecret" Icon="refresh"></MatIconButton>
                    @*https://github.com/SamProf/MatBlazor/issues/303
                        <MatTooltip Tooltip="You have to copy the secret now, because it cannot be retrieved anymore."></MatTooltip>*@
                    <MatIconButton Icon="info"></MatIconButton>
                </div>
                <div class="form-group">
                    <MatDatePicker @bind-Value="@currentSecret.Expiration" Label="Expiration" FullWidth="true" Minimum="@DateTime.Now"></MatDatePicker>
                </div>
            </fieldset>
        </EditForm>
    </MatDialogContent>
    <MatDialogActions>
        <MatButton OnClick="@(e => { isCreateApiResourceSecretDialogOpen = false; })">@L["Cancel"]</MatButton>
        <MatButton OnClick="@CreateSecret">Create API Resource Secret</MatButton>
    </MatDialogActions>
</MatDialog>

@code {
    int pageSize { get; set; } = 15;
    int currentPage { get; set; } = 0;

    bool isCurrentApiContextIdReadOnly = false;

    List<ApiResourceDto> apiResources;
    ApiResourceDto currentApiResource { get; set; } = new ApiResourceDto();
    Secret currentSecret { get; set; } = new Secret();

    #region OnInitializedAsync

    protected override async Task OnInitializedAsync()
    {
        await InitializeApiResourcesListAsync();
    }

    public async Task InitializeApiResourcesListAsync()
    {
        try
        {
            var apiResponse = await Http.GetNewtonsoftJsonAsync<ApiResponseDto<List<ApiResourceDto>>>($"api/Admin/ApiResources?pageSize={pageSize}&pageNumber={currentPage}");

            if (apiResponse.IsSuccessStatusCode)
            {
                matToaster.Add(apiResponse.Message, MatToastType.Success, L["Operation Successful"]);
                apiResources = apiResponse.Result;
            }
            else
                matToaster.Add(apiResponse.Message, MatToastType.Danger, L["Operation Failed"]);
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, L["Operation Failed"]);
        }
    }

    #endregion

    #region OpenUpsertApiResourceDialog

    class Selection
    {
        public bool IsSelected { get; set; }
        public string Name { get; set; }
        public string Value { get; set; }
    };

    bool isUpsertApiResourceDialogOpen = false;
    bool isInsertOperation;

    string labelUpsertDialogTitle;
    string labelUpsertDialogOkButton;
    List<Selection> jwtClaimSelections = new List<Selection>();
    List<Selection> tokenSigningAlgorithmsSelections = new List<Selection>();

    //See https://identityserver4.readthedocs.io/en/latest/topics/crypto.html
    string[] signingAlgorithms = new string[] { "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512" };


    void OpenUpsertApiResourceDialog(ApiResourceDto apiResource = null)
    {
        try
        {
            isInsertOperation = apiResource == null;

            currentApiResource = apiResource ?? new ApiResourceDto();

            // Update the UI
            if (isInsertOperation)
            {
                labelUpsertDialogTitle = L["New API Resource"];
                labelUpsertDialogOkButton = L["Create"];
            }
            else
            {
                labelUpsertDialogTitle = L["Edit {0}", currentApiResource.Name];
                labelUpsertDialogOkButton = L["Update"];
            }

            isCurrentApiContextIdReadOnly = !isInsertOperation;

            jwtClaimSelections.Clear();

            foreach (var info in typeof(JwtClaimTypes).GetFields().Where(x => x.IsStatic && x.IsLiteral))
            {
                jwtClaimSelections.Add(new Selection
                {
                    Name = $"{info.Name.Humanize(LetterCasing.Title)} ({info.GetValue(info)})",
                    Value = info.GetValue(info).ToString(),
                    IsSelected = currentApiResource.UserClaims.Contains(info.GetValue(info))
                });
            }

            tokenSigningAlgorithmsSelections.Clear();

            foreach (var sa in signingAlgorithms)
            {
                tokenSigningAlgorithmsSelections.Add(new Selection
                {
                    Name = sa,
                    Value = sa,
                    IsSelected = currentApiResource.AllowedAccessTokenSigningAlgorithms.Contains(sa)
                });
            }

            currentApiResource.CustomUserClaims = currentApiResource.GetCustomUserClaims().ToList();

            if (!isInsertOperation)
                currentApiResource.SaveState();

            isUpsertApiResourceDialogOpen = true;
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, L["Operation Failed"]);
        }
    }

    async Task UpdateEnabled(ApiResourceDto apiResource)
    {
        currentApiResource = apiResource;
        currentApiResource.Enabled = !currentApiResource.Enabled;
        isInsertOperation = false;
        await UpsertApiResource();
    }

    void CancelChanges()
    {
        currentApiResource.RestoreState();
        isUpsertApiResourceDialogOpen = false;
    }

    async Task UpsertApiResource()
    {
        try
        {
            if (currentApiResource == null)
            {
                matToaster.Add("ApiResource Creation Error", MatToastType.Danger, "New ApiResource not found");
                return;
            }

            if (isUpsertApiResourceDialogOpen)
            {
                currentApiResource.UserClaims = jwtClaimSelections.Where(i => i.IsSelected).Select(i => i.Value).ToList();
                currentApiResource.AllowedAccessTokenSigningAlgorithms = tokenSigningAlgorithmsSelections.Where(i => i.IsSelected).Select(i => i.Value).ToList();
                ((List<string>)currentApiResource.UserClaims).AddRange(currentApiResource.CustomUserClaims);
            }

            ApiResponseDto apiResponse;

            if (isInsertOperation)
                apiResponse = await Http.PostJsonAsync<ApiResponseDto>("api/Admin/ApiResource", currentApiResource);
            else
                apiResponse = await Http.PutJsonAsync<ApiResponseDto>("api/Admin/ApiResource", currentApiResource);

            if (apiResponse.IsSuccessStatusCode)
            {
                matToaster.Add(apiResponse.Message, MatToastType.Success);

                StateHasChanged();
            }
            else
                matToaster.Add(apiResponse.Message, MatToastType.Danger);


            await OnInitializedAsync();

            isUpsertApiResourceDialogOpen = false;
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, L["Operation Failed"]);
        }
        finally
        {
            currentApiResource.ClearState();
        }
    }

    #endregion

    #region OpenDeleteApiResourceDialog

    bool isDeleteApiResourceDialogOpen = false;

    void OpenDeleteApiResourceDialog(ApiResourceDto apiResource)
    {
        currentApiResource = apiResource;
        isDeleteApiResourceDialogOpen = true;
    }

    async Task DeleteApiResourceAsync()
    {
        try
        {
            var response = await Http.DeleteAsync($"api/Admin/ApiResource/{currentApiResource.Name}");
            if (response.StatusCode != (HttpStatusCode)Status200OK)
            {
                matToaster.Add("ApiResource Delete Failed", MatToastType.Danger);
                return;
            }

            matToaster.Add("API Resource Deleted", MatToastType.Success);
            await OnInitializedAsync();
            isDeleteApiResourceDialogOpen = false;
            StateHasChanged();
        }
        catch (Exception ex)
        {
            matToaster.Add(ex.GetBaseException().Message, MatToastType.Danger, L["Operation Failed"]);
        }
    }

    #endregion

    #region OpenDeleteApiResourceSecretDialog

    bool isDeleteApiResourceSecretDialogOpen = false;

    void OpenDeleteApiResourceSecretDialog(Secret secret)
    {
        currentSecret = secret;
        isDeleteApiResourceSecretDialogOpen = true;
    }

    void DeleteApiResourceSecret()
    {
        currentApiResource?.ApiSecrets.Remove(currentSecret);
        isDeleteApiResourceSecretDialogOpen = false;
    }

    #endregion

    #region OpenCreateApiResourceSecretDialogOpen

    bool isCreateApiResourceSecretDialogOpen = false;

    void OpenCreateApiResourceSecretDialogOpen()
    {
        currentSecret = new Secret();
        GenerateSecret();
        isCreateApiResourceSecretDialogOpen = true;
    }

    public void CreateSecret()
    {
        if (!string.IsNullOrWhiteSpace(currentSecret.Value))
        {
            currentSecret.Value = currentSecret.Value.ToSha512();

            if (string.IsNullOrWhiteSpace(currentSecret.Description))
                currentSecret.Description = $"Created on {DateTime.Now}";

            currentApiResource?.ApiSecrets.Add(currentSecret);
            isCreateApiResourceSecretDialogOpen = false;
        }
    }

    void GenerateSecret()
    {
        currentSecret.Value = IdentityModel.CryptoRandom.CreateUniqueId(32);
    }

    #endregion

}
